# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys
import os
import re
import xml.etree.cElementTree as ET
from tempfile import mktemp
import logging

import six

from ferenda import util
from .elements import UnicodeElement, CompoundElement, OrdinalElement, serialize


class Textbox(CompoundElement):
    def __init__(self, *args, **kwargs):
        assert 'top' in kwargs, "top attribute missing"
        assert 'left' in kwargs, "left attribute missing"
        assert 'width' in kwargs, "width attribute missing"
        assert 'height' in kwargs, "height attribute missing"
        assert 'font' in kwargs, "font attribute missing"

        self.top = int(kwargs['top'])
        self.left = int(kwargs['left'])
        self.width = int(kwargs['width'])
        self.height = int(kwargs['height'])
        self.right = self.left + self.width
        self.bottom = self.top + self.height

        self.__fontspecid = kwargs['font']
        self.__fontspec = kwargs['fontspec']

        del kwargs['top']
        del kwargs['left']
        del kwargs['width']
        del kwargs['height']
        del kwargs['font']
        del kwargs['fontspec']

        super(Textbox, self).__init__(*args, **kwargs)

    def __unicode__(self):
        return "".join(self)

    def getfont(self):
        return self.__fontspec[self.__fontspecid]


class Textelement(UnicodeElement):
    pass


class PDFReader(CompoundElement):
    def __init__(self):
        self.fontspec = {}
        self.log = logging.getLogger('pdfreader')

    def read(self, pdffile, workdir=None):
        self.filename = pdffile
        assert os.path.exists(pdffile), "PDF %s not found" % pdffile
        if not workdir:
            workdir = os.path.dirname(pdffile)
        basename = os.path.basename(pdffile)
        xmlfile = os.sep.join(
            (workdir, os.path.splitext(basename)[0] + ".xml"))

        if not util.outfile_is_newer([pdffile], xmlfile):
            tmppdffile = os.sep.join([workdir, basename])
            util.copy_if_different(pdffile, tmppdffile)
            # two pass coding: First use -c (complex) to extract
            # background pictures, then use -xml to get easy-to-parse
            # text with bounding boxes.
            cmd = "pdftohtml -nodrm -c %s" % tmppdffile
            self.log.debug("Converting: %s" % cmd)
            (returncode, stdout, stderr) = util.runcmd(cmd,
                                                       require_success=True)
            # print "RET: %s, STDOUT: %s, STDERR: %s" % (returncode,stdout,stderr)
            # we won't need the html files
            for f in os.listdir(workdir):
                if f.endswith(".html"):
                    os.unlink(workdir + os.sep + f)

            cmd = "pdftohtml -nodrm -xml %s" % tmppdffile
            self.log.debug("Converting: %s" % cmd)
            (returncode, stdout, stderr) = util.runcmd(cmd,
                                                       require_success=True)
        return self.parse_xml(xmlfile)

    def set_background_path():
        pass

    def parse_xml(self, xmlfile):
        self.log.debug("Loading %s" % xmlfile)
        assert os.path.exists(xmlfile), "XML %s not found" % xmlfile
        try:
            tree = ET.parse(xmlfile)
        except ET.ParseError as e:
            self.log.warning("'%s', working around" % e)
            #fix = PDFXMLFix()
            #fix.fix(xmlfile)
            tree = ET.parse(xmlfile)

        # for each page element
        for pageelement in tree.getroot():
            page = Page(number=int(pageelement.attrib['number']),  # always int?
                        width=int(pageelement.attrib['width']),
                        height=int(pageelement.attrib['height']),
                        background=None)
            background = "%s%03d.png" % (
                os.path.splitext(xmlfile)[0], page.number)
            if os.path.exists(background):
                page.background = background
            else:
                print(("Did not set %s as background: Doesn't exist" %
                      background))

            # print("Creating page %s" % page.number)
            assert pageelement.tag == "page", "Got <%s>, expected <page>" % page.tag
            for element in pageelement:
                if element.tag == 'fontspec':
                    self.fontspec[element.attrib['id']] = element.attrib
                elif element.tag == 'text':
                    # print ET.tostring(element)
                    attribs = element.attrib
                    attribs['fontspec'] = self.fontspec
                    b = Textbox(**attribs)
                    # eliminate "empty" textboxes
                    if element.text and element.text.strip() == "" and not element.getchildren():
                        # print "Skipping empty box"
                        continue

                    if element.text and element.text.strip():
                        b.append(Textelement(str(element.text)))
                    # The below loop could be done recursively to
                    # support arbitrarily deep nesting (if we change
                    # Textelement to be a non-unicode derived type),
                    # but pdftohtml should not create such XML (there
                    # is no such data in the PDF file)
                    for child in element:
                        grandchildren = child.getchildren()
                        # special handling of the <i><b> construct
                        if grandchildren != []:
                            #print "Grandchildren handling: %s '%s' '%s'" % (len(grandchildren),
                            #                                                child.text,
                            #                                                child.tail)
                            assert (len(grandchildren) == 1), "General grandchildren not supported"
                            if child.text:
                                Textelement(str(child.text), tag=child.tag)
                            b.append(Textelement(
                                str(grandchildren[0].text), tag="ib"))
                            if child.tail:
                                Textelement(str(child.tail), tag=child.tag)
                        else:
                            b.append(
                                Textelement(str(child.text), tag=child.tag))
                        if child.tail:
                            b.append(Textelement(str(child.tail)))
                    if element.tail and element.tail.strip():  # can this happen?
                        b.append(Textelement(str(element.tail)))
                    page.append(b)
            # done reading the page
            self.append(page)
        self.log.debug("PDFReader initialized: %d pages, %d fontspecs" %
                       (len(self), len(self.fontspec)))

    def avg_font_size(self):
        pass

    def median_font_size(self):
        pass

    def median_box_width(self, threshold=0):
        boxwidths = []
        for page in self:
            for box in page:
                if box.right - box.left < threshold:
                    continue
                # print "Box width: %d" % (box.right-box.left)
                boxwidths.append(box.right - box.left)
        boxwidths.sort()
        return boxwidths[int(len(boxwidths) / 2)]

    def common_horizontal_gutters():
        pass

    def common_vertical_gutters():
        pass


class Page(CompoundElement, OrdinalElement):
    # also has
    # self.width
    # self.height

    def vertical_gutters(self):
        return ((0, 42), (463, 482))

    def horizontal_gutters(self):
        return ((0, 42), (463, 482))

    # text: can be string, re obj or callable (gets called with the box obj)
    # fontsize: can be int or callable
    # fontname: can be string or callable
    # top,left,bottom,right
    def boundingbox(self, top=0, left=0, bottom=None, right=None):
        if not bottom:
            bottom = self.height
        if not right:
            right = self.width
        for box in self:
            # print u"    Examining [%dx%d][%dx%d] against constraints [%dx%d][%dx%d]" % (box.top,box.left,box.bottom,box.right, top,left,bottom,right)

            #if (box.top >= top): print "        Top OK"
            #if (box.left >= left): print "        Left OK"
            #if (box.bottom <= bottom): print "        Bottom OK"
            #if (box.right <= right): print "        Right OK"

            if (box.top >= top and
                box.left >= left and
                box.bottom <= bottom and
                    box.right <= right):
                #print "    SUCCESS"
                yield box
            #else:
            #    print "    FAIL"

    def crop(self, top=0, left=0, bottom=None, right=None):
        # Crop any text box that sticks out
        # Actually if top and left != 0, we need to adjust them
        newboxes = []
        for box in self.boundingbox(top, left, bottom, right):
            box.top = box.top - top
            box.left = box.left - left
            newboxes.append(box)
        self[:] = []
        self.extend(newboxes)
        self.width = right - left
        self.height = bottom - top
        # Then crop the background images... somehow
        if os.path.exists(self.background):
            cmdline = "convert %s -crop %dx%d+%d+%d +repage %s" % (self.background,
                                                                   self.width, self.height, left, top,
                                                                   self.background + ".new")
            # print "Running %s" % cmdline
            (returncode, stdout, stderr) = util.runcmd(cmdline,
                                                       require_success=True)
            util.replace_if_different(
                "%s.new" % self.background, self.background)

    def __unicode__(self):
        textexcerpt = " ".join([str(x) for x in self])
        return "Page %d (%d x %d): '%s...'" % (self.number, self.width, self.height, str(textexcerpt[:40]))

    def __str__(self):
        return str(self).encode('ascii')

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print(("Usage: %s pdffile" % (sys.argv[0])))
    else:
        r = PDFReader()
        r.read(sys.argv[1])
        for page in r:
            print((str(page)))
            for box in page.boundingbox(0, 500, 1029, 700):
                print(("    [%dx%d][%dx%d] %s" % (box.top,
                      box.left, box.bottom, box.right, str(box))))

        print((r.median_box_width(threshold=0)))


# Fixes a error with incorrectly nested tags often found in pdftohtml
# generated xml
if not six.PY3:
    import sgmllib
    from xml.sax.saxutils import escape as xml_escape
    import unicodedata

    class PDFXMLFix(sgmllib.SGMLParser):
        selfclosing = ["fontspec"]

        # preparations to remove invalid chars in handle_data
        all_chars = (unichr(i) for i in range(0x10000))
        control_chars = ''.join(
            c for c in all_chars if unicodedata.category(c) == 'Cc')
        # tab and newline are technically Control characters in
        # unicode, but we want to keep them.
        control_chars = control_chars.replace("\t", "").replace("\n", "")
        control_char_re = re.compile('[%s]' % re.escape(control_chars))

        def __init__(self):
            sgmllib.SGMLParser.__init__(self)
            self.tags = []
            self.fp = None

        def fix(self, filename):
            usetempfile = not self.fp

            if usetempfile:
                tmpfile = mktemp()
                self.fp = open(tmpfile, "w")

            self.fp.write('<?xml version="1.0" encoding="UTF-8"?>')

            f = open(filename)
            while True:
                s = f.read(8192)
                if not s:
                    break
                self.feed(s)
            self.close()

            if usetempfile:
                self.fp.close()
                if util.replace_if_different(tmpfile, filename):
                    print(("replaced %s with %s" % (filename, tmpfile)))
                else:
                    print(("%s was identical to %s" % (filename, tmpfile)))

        def close(self):
            sgmllib.SGMLParser.close(self)
            if self.tags:
                sys.stderr.write(
                    "WARNING: opened tag(s) %s not closed" % self.tags)
                self.fp.write(
                    "".join(["</%s>" % x for x in reversed(self.tags)]))

        def handle_decl(self, decl):
            # self.fp.write "Decl: ", decl
            self.fp.write("<!%s>" % decl)

        def handle_data(self, data):
            len_before = len(data)
            data = xml_escape(self.control_char_re.sub('', data))
            len_after = len(data)
            # self.fp.write "Data: ", data.strip()
            #if len_before != len_after:
            #    sys.stderr.write("WARNING: data changed from %s to %s chars: %r\n" % (len_before,len_after,data))
            self.fp.write(data)

        def unknown_starttag(self, start, attrs):
            # self.fp.write "Start: ", start, attrs
            if start in self.selfclosing:
                close = "/"
            else:
                close = ""
                self.tags.append(start)
                # sys.stderr.write(repr(self.tags)+"\n")
            if attrs:
                fmt = ['%s="%s"' % (x[0], x[1]) for x in attrs]
                self.fp.write("<%s %s%s>" % (start, " ".join(fmt), close))
            else:
                self.fp.write("<%s>" % start)

        def unknown_endtag(self, end):
            # sys.stderr.write(repr(self.tags)+"\n")
            start = self.tags.pop()
            if end != start and end in self.tags:
                # sys.stderr.write("%s is not %s, working around\n" % (end, start))
                self.fp.write("</%s>" % start)
                self.fp.write("</%s>" % end)
                self.fp.write("<%s>" % start)
            else:
                self.fp.write("</%s>" % end)
